package com.wjm.http;

import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.Socket;
import java.net.URLDecoder;
import java.util.HashMap;
import java.util.Map;

/**
 * Data:2022年08月06日
 * Author:wjm
 * Content:
 *
 *
 *  HttpRequest  表示请求对象
 *  在这个类上我们将请求的相关信息设计为属性来分别保存，并在构造方法中完成解析请求的工作
 *
 *  因为RequestHandle只需要做业务逻辑的控制，而具体的请求拆分我们需要单独封装到HttpRequest类中完成
 *  后期需要那一部分的内容，直接调用即可，实现了功能的扩展
 *
 */
public class HttpRequest {
    Socket socket;
    /*
    * 请求行
    * */
    private String method;   //请求方式
    private String url;      //抽象路径
    private String protocol; //版本信息

    /*
    * 消息头
    * */
    Map<String,String> map = new HashMap<>();

    /*
    * 消息正文
    * TODO
    * */

    public String getUrl() {
        return url;
    }

    //构造方法
    public HttpRequest(Socket socket) throws EmptyRequestException {
        this.socket = socket;
        //读取并拆分请求行
        parseRequestLine();
        //读取并拆分消息头
        parseHeaders();
        //读取并拆分消息正文
        parseContent();

    }

    /**
     * 读取并拆分消息正文
     * 将客户端传过来的参数（数据）存放进parameters集合中
     */
    private void parseContent() {
        System.out.println("消息正文开始解析...");
        //判断是否为post请求
        if ("post".equalsIgnoreCase(method)){//equalsIgnoreCase是忽略大小写


            /*
                如果是以post方式提交的表单，那么他的消息头就一定会有两个内容

                 Content-Length= 33 数据大小
                 Content-Type= application/x-www-form-urlencoded 携带的数据是以怎样的方式来提交的。
            */

            //从消息头集合map中获取键为Content-Length的值
            String length = map.get("Content-Length");
            //去除空白符否则类型转化会报错
            length = length.trim();

            //判断传来的消息正文中是否有数据
            if (length != null){
                //创建一个大小和Content-Length一样的byte数组，来读取参数
                int len = Integer.valueOf(length);
                byte[] data = new byte[len];

                //读取参数(数据)
                InputStream in = null;
                try {
                    in = socket.getInputStream();
                    in.read(data);
                } catch (IOException e) {
                    e.printStackTrace();
                }

                //从消息头集合map中获取键为Content-Type的值
                String type = map.get("Content-Type");
                //去除空白符，否则比对一直是false
                type = type.trim();
                if (type != null){
                    //判断该数据是否以表单的方式提交的
                    if ("application/x-www-form-urlencoded".equalsIgnoreCase(type)){
                        //如果是，那么它的格式才是这样的 username=zhangsan&password=1234&age=18&...
                        try {
                            String content = new String(data,"ISO8859-1");
                            System.out.println("拿到的正文是ISO8859-1:"+content);
                            //对content进行中文可视化
                            content = URLDecoder.decode(content,"utf-8");
                            System.out.println("拿到的正文是utf-8:"+content);
                            //解析content并存入到parameters集合中
                            parseParameter(content);

                        } catch (UnsupportedEncodingException e) {
                            e.printStackTrace();
                        }

                    }
                }

            }

        }
        System.out.println("消息正文解析完毕！");
    }


    /**
     *  读取一行字符串
     * @return 返回读取的一行字符串
     */
    private String readLine() {

        try{
            //获取输入流
            InputStream in = socket.getInputStream();
            //解析socket请求信息
            int a;
            //声明一个空字符串用,来装读到的字符
            StringBuilder stringBuilder = new StringBuilder();
            //申明两个变量用来表示当前读到的字符，和上一次读到的字符
            char cur = ' ';//当前的字符
            char pre = ' ';//上一个字符
            while ((a=in.read()) != -1){
                cur = (char)a;
                //判断是否读到一行的末尾,因为请求的每一行是以换行符和回车符为结尾的
                if (cur == 10 && pre == 13){
                    break;
                }
                //将当前的字符装到空字符串中
                stringBuilder.append(cur);
                //将当前字符赋值给上一个字符
                pre = cur;
            }
            //将stringBuilder转换为String类型，并去掉回车符CR
            return stringBuilder.toString().trim();


        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }
    }


    /**
     * 读取并拆分请求行
     */
    public void parseRequestLine() throws EmptyRequestException {
        //读取第一行字符串，也就是请求行
        String line = readLine();

        //判断该请求行是否为空请求，是的话抛出空请求异常
        if (line.isEmpty()){
            throw new EmptyRequestException("浏览器发送了一个空请求！");
        }
        //TODO 通过空格拆分请求行,找出请求资源路径
        String[] data = line.split("\\s");
        //请求方式
        method = data[0];
        //抽象路径
        url = data[1];
        //版本信息
        protocol = data[2];
        System.out.println(line);
        System.out.println("请求方式："+method);
        System.out.println("抽象路径："+url);
        System.out.println("版本信息："+protocol);
        //解析抽象路径url
        parseUrl();

    }


    //创建一个集合，用于存放url中客户端发送过来的数据
    private Map<String,String> parameters = new HashMap<>();
    private String requestURL; //表示url中？前的内容
    private String queryString;//表示url中？后的内容

    /**
     * 获取RequestURL的方法
     * @return 返回RequestURL
     */
    public String getRequestURL() {
        return requestURL;
    }

    /**
     * 这个一个通过指定的key获取parameters中对应的value的方法
     * @param key 指定的key
     * @return 对应的value，没有值则放回null
     */
    public String getParameters(String key){
        return parameters.get(key);
    }

    /**
     * 用于解析抽象路径url的方法
     * 将url中的路径和客户端发送的数据分开，
     * 将请求路径存入requestURL中，将数据以键值对的形式存入parameters集合中
     */
    private void parseUrl(){

        /*
            将拿到的字符用utf-8解码，也就是进行中文格式化
            因为最先拿到的url是ISO8859-1编码的
        */
        try {
            url = URLDecoder.decode(url,"utf-8");
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }

        //通过？来拆分url
        String[] data = url.split("\\?");
        //判断url中是否含有客户端传过来的数据，预防这种情况：webapps/myweb/index.html?
        if (data.length>1){//如果有数据则长度等于2
            requestURL = data[0];  //webapps/web/index
            queryString = data[1]; //username=wjm&password=123&age=18&...
            //解析queryString，并存入parameters集合中
            parseParameter(queryString);

        }else {//？后无数据，长度为1
            requestURL = url;
        }
        System.out.println("requestURL:"+requestURL);
        System.out.println("queryString:"+queryString);
        System.out.println("存放客户端传来的数据的parameters集合："+parameters);

    }


    /**
     * 解析参数
     * 拆分格式为：username=wjm&password=123&age=18&...的字符串，并存到parameters集合中
     * @param queryString 需要拆分的字符串
     */
    private void parseParameter(String queryString) {
        //对queryString按 & 拆分
        String[] str = queryString.split("&"); //[username=wjm,password=123,age=18...]
        //遍历数组
        for (String s : str){
            //对str数组中的每一个元素按=拆分
            String[] strings = s.split("=");
            /*
                    key         value

                    username    wjm
                    password    123
                    age         18
                    ...
            */
            //对strings数组进行判断，预防出现只有属性名没有属性值的情况，如username=
            if (strings.length>1){
                //将属性名，和属性值，以键值对的形式存入parameters集合
                parameters.put(strings[0],strings[1]);
            }else {//只有健没有值，那么值就存null
                parameters.put(strings[0],null);
            }
        }
    }

    /**
     * 读取并拆分请求头
     */
    public void parseHeaders(){
        /*
         * TODO 读取和拆分消息头
         *   key : value [CRLF]
         *   [CRLF]
         * */
        while (true){
            String str = readLine();
            //判断是否读到消息头的末尾
            if (str.isEmpty()){//因为消息头部分结束的标志是单独的在向服务器发送一个连续的标识符【回车符和换行符 CRLF】。
                break;
            }
            //System.out.println("消息头内容是："+str);
            //通过:冒号拆分读到的一行
            String[] data1 = str.split(":");
            //将读到的内容以键值对方式存入集合,消息头名称作为key，消息头名称的内容作为value
            map.put(data1[0],data1[1]);

        }
        //输出map集合
        System.out.println("拆分后的请求头"+map);
    }


}
